import pandas as pd
import numpy as np
import gensim
from gensim import corpora, models
import pyLDAvis.gensim
import matplotlib.pyplot as plt
import re
import jieba
import os
from tqdm import tqdm

# ========== 新增：配置中文字体 ==========
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示异常

# --------------------------
# 1. 数据加载与预处理
# --------------------------
try:
    df = pd.read_csv('douban_movie_comments.csv', encoding='utf-8-sig')
    print(f"成功加载原始评论数据，共 {len(df)} 条记录")
except FileNotFoundError:
    print("错误：未找到 douban_movie_comments.csv，请确认文件路径正确")
    exit()

# 情感映射（与情感分析代码保持一致）
df['评分'] = pd.to_numeric(df['评分'], errors='coerce')
def map_sentiment(score):
    if pd.isna(score): 
        return 'neutral'
    elif score <= 2:
        return 'negative'
    elif score == 3:
        return 'neutral'
    else: 
        return 'positive'
df['sentiment'] = df['评分'].apply(map_sentiment)

# 文本清洗与分词
stopwords = set([
    '的', '了', '是', '在', '这', '都', '让', '我们', '一种', '不是', '没有', '更',
    '但', '与', '中', '为', '用', '当', '于', '以', '将', '成', '对', '自己','很多',
    '就是','什么','最后','《', '》', '：', '——', '、', ',', '.', '！', '？', '看',
    '非常', '很', '也', '还', '就', '有', '把', '给', '说', '你', '我', '她', '他',
    '它', '一个', '一些', '一点','因为','有点','他们',
    '电影', '影片', '觉得', '剧情', '故事', '画面', '演员', '演技', '镜头', '配乐',
    '特效', '导演', '编剧', '类型', '题材', '票房', '上映',
    '观众', '影院', '系列', '续集', '前传', '改编', '原著', '小说', '漫画', '游戏',
    'IP', '角色', '人物', '主角', '配角', '反派','作品','一部',
    '知道', '了解', '认识', '喜欢', '讨厌', '想要', '需要', '应该', '可以', '能够',
    '会', '要', '没', '不', '别', '太', '挺', '比较', '稍微','怎么','还是','还有',
    '那么','这种','这个','这样','但是','这么','感觉','为了','现在','不停','可能'
])

def preprocess_text(text):
    if pd.isna(text) or not isinstance(text, str):
        return []
    text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9\s]', '', str(text).strip())
    text = re.sub(r'\s+', ' ', text)
    words = jieba.lcut(text)
    return [word for word in words if word not in stopwords and len(word) > 1 and not word.isdigit()]

# 按情感分类文本
positive_docs = df[df['sentiment'] == 'positive']['评论内容'].apply(preprocess_text).tolist()
negative_docs = df[df['sentiment'] == 'negative']['评论内容'].apply(preprocess_text).tolist()

# --------------------------
# 2. 主题建模（LDA）
# --------------------------
def train_lda(docs, num_topics=5):
    if not docs or all(len(doc) == 0 for doc in docs):
        return None, None, None
    # 构建词典和语料库
    dictionary = corpora.Dictionary(docs)
    corpus = [dictionary.doc2bow(doc) for doc in docs]
    # 训练LDA模型
    lda_model = models.LdaModel(
        corpus, 
        num_topics=num_topics, 
        id2word=dictionary, 
        passes=10,
        random_state=42
    )
    return lda_model, dictionary, corpus

# 训练正面评论LDA模型
print("训练正面评论LDA模型...")
pos_lda, pos_dict, pos_corpus = train_lda(positive_docs, num_topics=3)
# 训练负面评论LDA模型
print("训练负面评论LDA模型...")
neg_lda, neg_dict, neg_corpus = train_lda(negative_docs, num_topics=3)

# --------------------------
# 3. 困惑度计算（选择最佳主题数）
# --------------------------
def calculate_perplexity(lda, corpus, dictionary):
    if lda is None:
        return float('inf')
    return lda.log_perplexity(corpus)

# 正面评论主题数选择
if pos_lda:
    pos_perplexities = []
    for num in range(2, 6):
        lda = models.LdaModel(pos_corpus, num_topics=num, id2word=pos_dict, passes=5, random_state=42)
        pos_perplexities.append(calculate_perplexity(lda, pos_corpus, pos_dict))
    best_pos_topics = 2 + np.argmin(pos_perplexities)
    print(f"正面评论最佳主题数：{best_pos_topics}")
    pos_lda, pos_dict, pos_corpus = train_lda(positive_docs, num_topics=best_pos_topics)

# 负面评论主题数选择
if neg_lda:
    neg_perplexities = []
    for num in range(2, 6):
        lda = models.LdaModel(neg_corpus, num_topics=num, id2word=neg_dict, passes=5, random_state=42)
        neg_perplexities.append(calculate_perplexity(lda, neg_corpus, neg_dict))
    best_neg_topics = 2 + np.argmin(neg_perplexities)
    print(f"负面评论最佳主题数：{best_neg_topics}")
    neg_lda, neg_dict, neg_corpus = train_lda(negative_docs, num_topics=best_neg_topics)

# --------------------------
# 4. 可视化主题（pyLDAvis）
# --------------------------
def visualize_lda(lda, corpus, dictionary, title):
    if lda is None:
        print(f"无{title}数据，跳过可视化")
        return
    vis_data = pyLDAvis.gensim.prepare(lda, corpus, dictionary)
    pyLDAvis.save_html(vis_data, f'{title}_lda_vis.html')
    print(f"{title}主题可视化已保存为 {title}_lda_vis.html")

if pos_lda:
    visualize_lda(pos_lda, pos_corpus, pos_dict, "正面评论")
if neg_lda:
    visualize_lda(neg_lda, neg_corpus, neg_dict, "负面评论")

# --------------------------
# 5. 绘制困惑度曲线
# --------------------------
def plot_perplexity_curve(perplexities, title):
    if not perplexities:
        return
    plt.figure(figsize=(8, 5))
    plt.plot(range(2, 2 + len(perplexities)), perplexities, marker='o')
    plt.title(f'{title}主题数-困惑度曲线', fontsize=14)  # 此处标题已支持中文
    plt.xlabel('主题数', fontsize=12)
    plt.ylabel('困惑度', fontsize=12)
    plt.xticks(range(2, 2 + len(perplexities)))
    plt.grid(True, linestyle='--', alpha=0.7)
    plt.savefig(f'{title}_perplexity_curve.png', dpi=300, bbox_inches='tight')
    plt.show()

if pos_lda:
    plot_perplexity_curve(pos_perplexities, "正面评论")
if neg_lda:
    plot_perplexity_curve(neg_perplexities, "负面评论")

# --------------------------
# 6. 主题-情感倾向分析
# --------------------------
def get_topic_sentiment(lda, corpus):
    if lda is None:
        return {}
    topic_sentiments = {}
    for i, doc in enumerate(corpus):
        topics = lda.get_document_topics(doc, minimum_probability=0.1)
        for topic_id, prob in topics:
            if topic_id not in topic_sentiments:
                topic_sentiments[topic_id] = []
            topic_sentiments[topic_id].append(prob)
    # 计算每个主题的平均概率（代表情感倾向强度）
    for topic_id, probs in topic_sentiments.items():
        topic_sentiments[topic_id] = np.mean(probs)
    return topic_sentiments

if pos_lda:
    pos_topic_sent = get_topic_sentiment(pos_lda, pos_corpus)
    print("\n正面评论各主题情感倾向：")
    for topic_id, prob in pos_topic_sent.items():
        print(f"主题{topic_id}：{prob:.4f}")

if neg_lda:
    neg_topic_sent = get_topic_sentiment(neg_lda, neg_corpus)
    print("\n负面评论各主题情感倾向：")
    for topic_id, prob in neg_topic_sent.items():
        print(f"主题{topic_id}：{prob:.4f}")

# --------------------------
# 7. 输出主题关键词
# --------------------------
def print_topic_keywords(lda, dictionary, num_words=5):
    if lda is None:
        return
    for topic_id in range(lda.num_topics):
        keywords = lda.show_topic(topic_id, topn=num_words)
        print(f"主题{topic_id}关键词：{', '.join([word for word, _ in keywords])}")

print("\n正面评论主题关键词：")
if pos_lda:
    print_topic_keywords(pos_lda, pos_dict)

print("\n负面评论主题关键词：")
if neg_lda:
    print_topic_keywords(neg_lda, neg_dict)